#include "rules.h"

#include "util/common_macros.h"		// macros
#include "util/log.h"		// macros
#include "util/mmgr.h"		// Needs to come in early

using namespace lang;


Rules& Rules::Instance()
{
	static Rules inst;
	return inst;
}

void Rules::SetLanguage(const std::string& Lang)
{
	UTIL_SAFE_DELETE(m_pRule);
	m_CurrentLanguage = Lang;
//	setlocale(LC_ALL, Lang.c_str()); // ADRIAN: for some reason, setlocale interferes with LUA... more specifically, something inside ScriptHelper::DoFile()

	if (strcmpi("japanese", Lang.c_str()) == 0)
	{
		m_pRule = new Rule_KinsokuShori();
	}else
	if (strcmpi("english", Lang.c_str()) == 0)
	{
		m_pRule = new Rule_GenericLatin();
	}
}

int Rules::ApplyRules(const std::wstring& Buffer, int From, int To)
{
	if (m_pRule) return m_pRule->ApplyRules(Buffer, From, To);
	return To;
}

Rules::Rules() : m_pRule(NULL),
	m_CurrentLanguage("")
{
}

Rules::~Rules()
{
	UTIL_SAFE_DELETE(m_pRule);
}



int RuleData::ApplyRules(const std::wstring& Buffer, int From, int To)
{
	int Start = From;
	int End = To;

	while ((End > Start) && !CanBreakHere(Buffer, End))
	{
		if ((m_JumpToIndex >= 0) && (m_JumpToIndex < End))
			End = m_JumpToIndex;
		else
			End--;
		m_JumpToIndex = -1;
	}

	if (End <= Start)
	{
		// what are you kidding me???
		return To;
	}

	return End;
}

bool RuleData::IsInList(unsigned short ch, unsigned short* pList, int Count)
{
	for (int i = 0; i < Count; i++)
	{
		if (ch == pList[i])
			return true;
	}
	return false;
}



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Apply Japanese Kinsoku Shori rules
//

Rule_KinsokuShori::Rule_KinsokuShori()
{
	// any better way to insert phrases like this???
	unsigned short ch[] = { 0x306D, 0x308B };
	m_GrammaticalHiragana.push_back((wchar_t*) ch);
}

bool Rule_KinsokuShori::CanBreakHere(const std::wstring& Buffer, int Idx)
{
	static unsigned short NO_START_CHAR[] = { 0x2C, 0x29, 0x5D, 0xFF5D, 0x3001, 0x3015, 0x3009, 0x300B, 0x300D, 0x300F, 0x3011, 0x3019, 0x3017, 0x301F, 0x2019, 0x201D, 0xFF60, 0xBB, 0x30FD, 0x30FE, 0x30FC, 0x30A1, 0x30A3, 0x30A5, 0x30A7, 0x30A9, 0x30C3, 0x30E3, 0x30E5, 0x30E7, 0x30EE, 0x30F5, 0x30F6, 0x3041, 0x3043, 0x3045, 0x3047, 0x3049, 0x3063, 0x3083, 0x3085, 0x3087, 0x308E, 0x3095, 0x3096, 0x31F0, 0x31F1, 0x31F2, 0x31F3, 0x31F4, 0x31F5, 0x31F6, 0x31F7, 0x31F8, 0x31F9, 0x31FA, 0x31FB, 0x31FC, 0x31FD, 0x31FE, 0x31FF, 0x3005, 0x303B, 0x2010, 0x30A0, 0x2013, 0x301C, 0x3F, 0x21, 0x203C, 0x2047, 0x2048, 0x2049, 0x30FB, 0x3A, 0x3B, 0x3002, 0x2E };
	static unsigned short NO_END_CHAR[] = { 0x28, 0x5B, 0xFF5B, 0x3014, 0x3008, 0x300A, 0x300C, 0x300E, 0x3010, 0x3018, 0x3016, 0x301D, 0x2018, 0x201C, 0xFF5F, 0xAB };
	static unsigned short NO_SPLIT_CHAR[] = { 0x2014, 0x2026, 0x2025, 0x3033, 0x3034, 0x3035 };

	static int NO_START_CHAR_COUNT = sizeof(NO_START_CHAR) / sizeof(unsigned short);
	static int NO_END_CHAR_COUNT = sizeof(NO_END_CHAR) / sizeof(unsigned short);
	static int NO_SPLIT_CHAR_COUNT = sizeof(NO_SPLIT_CHAR) / sizeof(unsigned short);

	unsigned short ch, prev;

	// check next line does not start with illegal character
	ch = Buffer[Idx];
	if (IsInList(ch, NO_START_CHAR, NO_START_CHAR_COUNT)) return false;

	// check this line does not end with illegal character
	if (Idx > 0)
	{
		ch = Buffer[Idx-1];
		if (IsInList(ch, NO_END_CHAR, NO_END_CHAR_COUNT)) return false;
	}

	// check splitting rules
	if (Idx > 0)
	{
		ch = Buffer[Idx];
		prev = Buffer[Idx-1];

		/* edit -- umm, no, doesn't work
		if (iswpunct(ch) && (Idx < (int)(Buffer.size() - 1)))
		{
			unsigned short next = Buffer[Idx+1];
			if (!iswdigit(next)) return true; // can always break at punctuation except for numbers
		}
		*/

		if (IsInList(ch, NO_SPLIT_CHAR, NO_SPLIT_CHAR_COUNT) && IsInList(prev, NO_SPLIT_CHAR, NO_SPLIT_CHAR_COUNT)) return false;
		//if (iswdigit(ch) && iswdigit(prev)) return false;
		if (iswdigit(ch) || iswdigit(prev)) return false; // don't break at a number - character before or after number could be attached to it
		if (IsKanji(ch) && IsKanji(prev)) return false; // don't split consecutive Kanji???
		if (IsKatakana(ch) && IsKatakana(prev)) return false; // don't split consecutive Katakana???
		if (IsHiragana(ch) && IsHiragana(prev)) return false; // don't split consecutive Hiragana???
		if (IsKanji(prev) && IsGrammaticalHiragana(Buffer, Idx)) return false; // don't split this type of Hiragana with attached Kanji
	}

	return true;
}

bool Rule_KinsokuShori::IsKanji(unsigned short ch)
{
	return UTIL_RANGE(ch, 0x4E00, 0x9FBF);
}

bool Rule_KinsokuShori::IsHiragana(unsigned short ch)
{
	return UTIL_RANGE(ch, 0x3040, 0x309F);
}

bool Rule_KinsokuShori::IsKatakana(unsigned short ch)
{
	return UTIL_RANGE(ch, 0x30A0, 0x30FF);
}

bool Rule_KinsokuShori::IsGrammaticalHiragana(const std::wstring& Buffer, int Idx)
{
	int Size = (int)Buffer.size();
	int Len = Size - Idx + 1;
	int WordLen;
	for (int i = 0; i < (int)m_GrammaticalHiragana.size(); i++)
	{
		std::wstring& Word = m_GrammaticalHiragana[i];
		WordLen = (int)Word.length();

		if (Len < WordLen) continue;
#ifdef _PLATFORM_WIN32
		if (_wcsnicmp(Word.c_str(), (wchar_t*)&Buffer.c_str()[Idx], WordLen) == 0)	return true;
#endif //_PLATFORM_WIN32
#ifdef _PLATFORM_WII
		if (wcsnicmp(Word.c_str(), (wchar_t*)&Buffer.c_str()[Idx], WordLen) == 0)	return true;
#endif //_PLATFORM_WII
	}

	return false;
}



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Apply generic latin text rules
//

bool Rule_GenericLatin::CanBreakHere(const std::wstring& Buffer, int Idx)
{
	unsigned short ch, prev;

	// assume most punctuation breaks already handled using whitespace;
	// just need to check a few exceptions and for non-breaking text

	if (Idx > 0)
	{
		ch = Buffer[Idx];
		prev = Buffer[Idx-1];

		if ((prev == 0x2D) || (prev == 0x2013)) // hyphen and long hyphen as line end
			return true;
		if ((ch == 0x2D) || (ch == 0x2013)) // hyphen and long hyphen as line start
			return false;

		// check splitting rules
		if (iswdigit(ch) && iswdigit(prev)) return false;
		if (iswalpha(ch) && iswalpha(prev)) return false;
	}
	return true;
}
